﻿
#include "BoundingBox.h"

#include <System/Int32.h>
#include <System/ArgumentOutOfRangeException.h>
#include <System/ArgumentNullException.h>
#include <System/ArgumentException.h>

#include <Riccsson/Xna/Framework/BoundingFrustum.h>
#include <Riccsson/Xna/Framework/BoundingSphere.h>
#include <Riccsson/Xna/Framework/Ray.h>
#include <Riccsson/Xna/Framework/Plane.h>

using namespace Riccsson::System;
using namespace Riccsson::Xna::Framework;

BoundingBox::BoundingBox(Vector3 min, Vector3 max)
{
	this->Min = min;
	this->Max = max;
}
ContainmentType BoundingBox::Contains(BoundingBox box)
{
	//test if all corner is in the same side of a face by just checking min and max
	if (box.Max.X < Min.X
		|| box.Min.X > Max.X
		|| box.Max.Y < Min.Y
		|| box.Min.Y > Max.Y
		|| box.Max.Z < Min.Z
		|| box.Min.Z > Max.Z)
		return ContainmentType::Disjoint;


	if (box.Min.X >= Min.X
		&& box.Max.X <= Max.X
		&& box.Min.Y >= Min.Y
		&& box.Max.Y <= Max.Y
		&& box.Min.Z >= Min.Z
		&& box.Max.Z <= Max.Z)
		return ContainmentType::Contains;

	return ContainmentType::Intersects;
}
void BoundingBox::Contains(BoundingBox ref box, ContainmentType out result)
{
	result = Contains(box);
}
ContainmentType BoundingBox::Contains(BoundingFrustum& frustum)
{
	//TODO: bad done here need a fix. 
	//Because question is not frustum contain box but reverse and this is not the same
	int i;
	ContainmentType contained;
	GC_PTR<Riccsson::System::Array<Vector3>> corners = frustum.GetCorners();

	// First we check if frustum is in box
	for (i = 0; i < corners->Length; i++)
	{
		this->Contains(/*ref*/ (**corners)[i], /*out*/ contained);
		if (contained == ContainmentType::Disjoint)
			break;
	}

	if (i == corners->Length) // This means we checked all the corners and they were all contain or instersect
		return ContainmentType::Contains;

	if (i != 0)             // if i is not equal to zero, we can fastpath and say that this box intersects
		return ContainmentType::Intersects;


	// If we get here, it means the first (and only) point we checked was actually contained in the frustum.
	// So we assume that all other points will also be contained. If one of the points is disjoint, we can
	// exit immediately saying that the result is Intersects
	i++;
	for (; i < corners->Length; i++)
	{
		this->Contains(/*ref*/ (**corners)[i], /*out*/ contained);
		if (contained != ContainmentType::Contains)
			return ContainmentType::Intersects;

	}

	// If we get here, then we know all the points were actually contained, therefore result is Contains
	return ContainmentType::Contains;
}
ContainmentType BoundingBox::Contains(BoundingSphere& sphere)
{
	if (sphere.Center.X - Min.X >= sphere.Radius
		&& sphere.Center.Y - Min.Y >= sphere.Radius
		&& sphere.Center.Z - Min.Z >= sphere.Radius
		&& Max.X - sphere.Center.X >= sphere.Radius
		&& Max.Y - sphere.Center.Y >= sphere.Radius
		&& Max.Z - sphere.Center.Z >= sphere.Radius)
		return ContainmentType::Contains;

	double dmin = 0;

	double e = sphere.Center.X - Min.X;
	if (e < 0)
	{
		if (e < -sphere.Radius)
		{
			return ContainmentType::Disjoint;
		}
		dmin += e * e;
	}
	else
	{
		e = sphere.Center.X - Max.X;
		if (e > 0)
		{
			if (e > sphere.Radius)
			{
				return ContainmentType::Disjoint;
			}
			dmin += e * e;
		}
	}

	e = sphere.Center.Y - Min.Y;
	if (e < 0)
	{
		if (e < -sphere.Radius)
		{
			return ContainmentType::Disjoint;
		}
		dmin += e * e;
	}
	else
	{
		e = sphere.Center.Y - Max.Y;
		if (e > 0)
		{
			if (e > sphere.Radius)
			{
				return ContainmentType::Disjoint;
			}
			dmin += e * e;
		}
	}

	e = sphere.Center.Z - Min.Z;
	if (e < 0)
	{
		if (e < -sphere.Radius)
		{
			return ContainmentType::Disjoint;
		}
		dmin += e * e;
	}
	else
	{
		e = sphere.Center.Z - Max.Z;
		if (e > 0)
		{
			if (e > sphere.Radius)
			{
				return ContainmentType::Disjoint;
			}
			dmin += e * e;
		}
	}

	if (dmin <= sphere.Radius * sphere.Radius)
		return ContainmentType::Intersects;

	return ContainmentType::Disjoint;
}
void BoundingBox::Contains(BoundingSphere ref sphere, ContainmentType out result)
{
	result = this->Contains(sphere);
}
ContainmentType BoundingBox::Contains(Vector3 point)
{
	ContainmentType result;
	this->Contains(/*ref*/ point, /*out*/ result);
	return result;
}
void BoundingBox::Contains(Vector3 ref point, ContainmentType out result)
{
	//first we get if point is out of box
	if (point.X < this->Min.X
		|| point.X > this->Max.X
		|| point.Y < this->Min.Y
		|| point.Y > this->Max.Y
		|| point.Z < this->Min.Z
		|| point.Z > this->Max.Z)
	{
		result = ContainmentType::Disjoint;
	}//or if point is on box because coordonate of point is lesser or equal
	else if (point.X == this->Min.X
		|| point.X == this->Max.X
		|| point.Y == this->Min.Y
		|| point.Y == this->Max.Y
		|| point.Z == this->Min.Z
		|| point.Z == this->Max.Z)
		result = ContainmentType::Intersects;
	else
		result = ContainmentType::Contains;


}
BoundingBox BoundingBox::CreateFromPoints(Riccsson::System::Collections::Generic::IEnumerable<Vector3>* points)
{
	if (points == null)
		throw Riccsson::System::ArgumentNullException();

	// TODO: Just check that Count > 0
	bool empty = true;
	Vector3 vector2 = Vector3(Int32::MaxValue);	// Expected: Float::MaxValue
	Vector3 vector1 = Vector3(Int32::MinValue);	// Expected: Float::MinValue
	foreach2 (var, vector3, points)
	{
		vector2 = Vector3::Min(vector2, *vector3);
		vector1 = Vector3::Max(vector1, *vector3);
		empty = false;
	}
	if (empty)
		throw Riccsson::System::ArgumentException();

	return BoundingBox(vector2, vector1);
}
BoundingBox BoundingBox::CreateFromSphere(BoundingSphere& sphere)
{
	Vector3 vector1 = Vector3(sphere.Radius);
	return BoundingBox(sphere.Center - vector1, sphere.Center + vector1);
}
void BoundingBox::CreateFromSphere(BoundingSphere ref sphere, BoundingBox out result)
{
	result = BoundingBox::CreateFromSphere(sphere);
}
BoundingBox BoundingBox::CreateMerged(BoundingBox original, BoundingBox additional)
{
	return BoundingBox(
		Vector3::Min(original.Min, additional.Min), Vector3::Max(original.Max, additional.Max));
}
void BoundingBox::CreateMerged(BoundingBox ref original, BoundingBox ref additional, BoundingBox out result)
{
	result = BoundingBox::CreateMerged(original, additional);
}
bool BoundingBox::Equals(BoundingBox* other)
{
	return (this->Min == other->Min) && (this->Max == other->Max);
}
bool BoundingBox::Equals(Riccsson::System::object* obj)
{
	return (instanceof(obj, BoundingBox)) ? this->Equals((BoundingBox*)obj) : false;
}
GC_PTR<Riccsson::System::Array<Vector3>> BoundingBox::GetCorners()
{
	GC_PTR<Riccsson::System::Array<Vector3>> output( new Riccsson::System::Array<Vector3>(8) );

	(**output)[0] = Vector3(this->Min.X, this->Max.Y, this->Max.Z); 
	(**output)[1] = Vector3(this->Max.X, this->Max.Y, this->Max.Z);
	(**output)[2] = Vector3(this->Max.X, this->Min.Y, this->Max.Z); 
	(**output)[3] = Vector3(this->Min.X, this->Min.Y, this->Max.Z); 
	(**output)[4] = Vector3(this->Min.X, this->Max.Y, this->Min.Z);
	(**output)[5] = Vector3(this->Max.X, this->Max.Y, this->Min.Z);
	(**output)[6] = Vector3(this->Max.X, this->Min.Y, this->Min.Z);
	(**output)[7] = Vector3(this->Min.X, this->Min.Y, this->Min.Z);

	return output;
}
void BoundingBox::GetCorners(Riccsson::System::Array<Vector3>* corners)
{
	if (corners == null)
	{
		throw Riccsson::System::ArgumentNullException("corners");
	}
	if (corners->Length < 8)
	{
		throw Riccsson::System::ArgumentOutOfRangeException("corners", "Not Enought Corners");
	}
	(*corners)[0].X = this->Min.X;
	(*corners)[0].Y = this->Max.Y;
	(*corners)[0].Z = this->Max.Z;
	(*corners)[1].X = this->Max.X;
	(*corners)[1].Y = this->Max.Y;
	(*corners)[1].Z = this->Max.Z;
	(*corners)[2].X = this->Max.X;
	(*corners)[2].Y = this->Min.Y;
	(*corners)[2].Z = this->Max.Z;
	(*corners)[3].X = this->Min.X;
	(*corners)[3].Y = this->Min.Y;
	(*corners)[3].Z = this->Max.Z;
	(*corners)[4].X = this->Min.X;
	(*corners)[4].Y = this->Max.Y;
	(*corners)[4].Z = this->Min.Z;
	(*corners)[5].X = this->Max.X;
	(*corners)[5].Y = this->Max.Y;
	(*corners)[5].Z = this->Min.Z;
	(*corners)[6].X = this->Max.X;
	(*corners)[6].Y = this->Min.Y;
	(*corners)[6].Z = this->Min.Z;
	(*corners)[7].X = this->Min.X;
	(*corners)[7].Y = this->Min.Y;
	(*corners)[7].Z = this->Min.Z;
}
int BoundingBox::GetHashCode()
{
	return this->Min.GetHashCode() + this->Max.GetHashCode();
}
bool BoundingBox::Intersects(BoundingBox box)
{
	bool result;
	Intersects(/*ref*/ box, /*out*/ result);
	return result;
}
void BoundingBox::Intersects(BoundingBox ref box, bool out result)
{
	if ((this->Max.X >= box.Min.X) && (this->Min.X <= box.Max.X))
	{
		if ((this->Max.Y < box.Min.Y) || (this->Min.Y > box.Max.Y))
		{
			result = false;
			return;
		}

		result = (this->Max.Z >= box.Min.Z) && (this->Min.Z <= box.Max.Z);
		return;
	}

	result = false;
	return;
}
bool BoundingBox::Intersects(BoundingFrustum& frustum)
{
	return frustum.Intersects(*this);
}
bool BoundingBox::Intersects(BoundingSphere& sphere)
{
	if (sphere.Center.X - Min.X > sphere.Radius
		&& sphere.Center.Y - Min.Y > sphere.Radius
		&& sphere.Center.Z - Min.Z > sphere.Radius
		&& Max.X - sphere.Center.X > sphere.Radius
		&& Max.Y - sphere.Center.Y > sphere.Radius
		&& Max.Z - sphere.Center.Z > sphere.Radius)
		return true;

	double dmin = 0;

	if (sphere.Center.X - Min.X <= sphere.Radius)
		dmin += (sphere.Center.X - Min.X) * (sphere.Center.X - Min.X);
	else if (Max.X - sphere.Center.X <= sphere.Radius)
		dmin += (sphere.Center.X - Max.X) * (sphere.Center.X - Max.X);

	if (sphere.Center.Y - Min.Y <= sphere.Radius)
		dmin += (sphere.Center.Y - Min.Y) * (sphere.Center.Y - Min.Y);
	else if (Max.Y - sphere.Center.Y <= sphere.Radius)
		dmin += (sphere.Center.Y - Max.Y) * (sphere.Center.Y - Max.Y);

	if (sphere.Center.Z - Min.Z <= sphere.Radius)
		dmin += (sphere.Center.Z - Min.Z) * (sphere.Center.Z - Min.Z);
	else if (Max.Z - sphere.Center.Z <= sphere.Radius)
		dmin += (sphere.Center.Z - Max.Z) * (sphere.Center.Z - Max.Z);

	if (dmin <= sphere.Radius * sphere.Radius)
		return true;

	return false;
}
void BoundingBox::Intersects(BoundingSphere ref sphere, bool out result)
{
	result = Intersects(sphere);
}
PlaneIntersectionType BoundingBox::Intersects(Plane& plane)
{
	PlaneIntersectionType result;
	Intersects(/*ref*/ plane, /*out*/ result);
	return result;
}
void BoundingBox::Intersects(Plane ref plane, PlaneIntersectionType out result)
{
	// See http://zach.in.tu-clausthal.de/teaching/cg_literatur/lighthouse3d_view_frustum_culling/index.html

	Vector3 positiveVertex;
	Vector3 negativeVertex;

	if (plane.Normal.X >= 0)
	{
		positiveVertex.X = Max.X;
		negativeVertex.X = Min.X;
	}
	else
	{
		positiveVertex.X = Min.X;
		negativeVertex.X = Max.X;
	}

	if (plane.Normal.Y >= 0)
	{
		positiveVertex.Y = Max.Y;
		negativeVertex.Y = Min.Y;
	}
	else
	{
		positiveVertex.Y = Min.Y;
		negativeVertex.Y = Max.Y;
	}

	if (plane.Normal.Z >= 0)
	{
		positiveVertex.Z = Max.Z;
		negativeVertex.Z = Min.Z;
	}
	else
	{
		positiveVertex.Z = Min.Z;
		negativeVertex.Z = Max.Z;
	}

	var distance = Vector3::Dot(plane.Normal, negativeVertex) + plane.D;
	if (distance > 0)
	{
		result = PlaneIntersectionType::Front;
		return;
	}

	distance = Vector3::Dot(plane.Normal, positiveVertex) + plane.D;
	if (distance < 0)
	{
		result = PlaneIntersectionType::Back;
		return;
	}

	result = PlaneIntersectionType::Intersecting;
}
Riccsson::System::Nullable<float> BoundingBox::Intersects(Ray& ray)
{
	return ray.Intersects(*this);
}
void BoundingBox::Intersects(Ray ref ray, Nullable<float> out result)
{
	result = Intersects(ray);
}
GC_PTR<string> BoundingBox::ToString()
{
	//return string::Format("{{Min:{0} Max:{1}}}", this->Min.ToString(), this->Max.ToString());
	throw;
}